Serverless FrameworkとExpressを使用してREST APIをデプロイする

Serverless FrameworkとExpressを使用してREST APIをデプロイする

Clock Icon2021.06.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Many people are using serverless architecture to deploy web applications these days because of its very fast deployment, dynamic scaling, and pay-per-invocation.

This blog is about deploying a serverless REST API using Node.js, serverless and express framework.

このブログでは、Node.js と Serverless Framework と Express をつかった、サーバーレス REST API のデプロイについてに紹介します。

Serverless Framework とは?

Serverless Framework とは Function-as-a-service の開発やデプロイを簡単に実行することができるオープンソースのツールです。

関数を作成するのに必要なコードとインフラを管理しています。

3 つの大事なコンポーネントがあります。

  1. 関数

    プログラムのコードです。たとえばユーザー情報をデータベースに保存します。

  2. イベント

    AWS Lambda Function のトリガーです。たとえば AWS API Gateway の HTTP リクエストなどです。

  3. リソース

    インフラストラクチャコンポーネントです。たとえばユーザーデータを保存する AWS の DynamoDB です。

Express フレームワークとは?

Express はとても人気の Node.js のフレームワークです。Node.js のコードを簡単にかくことができます。

GET や POST などのルーティングを簡単に書けます

Handlebars などの View Rendering エンジンや Middleware との統合がかんたんにできます。

Rendering と configuration のためにテンプレート engine(たとえば handlebars や ejs)と env variable をセットすることができます

さいしょに

Node.js と Serverless Framework をインストールするひつようがあります。

awscli を configure することがたいせつです

エクスプレスフレームワークのきそちしきことがたいせつです

API について

  • この REST API の Backend はサーバーレスです
  • ユーザー ID を保存するために CRUD(Create,Read,Update,Delete)のオペレーションをします
  • Lambda と API Gateway と DynamoDB は Serverless Framework によってデプロイされます
  • API のコードはGitHubにあります

Express の作成

プロジェクトのために Directory をつくって、Express-Generator を使用して Application の Skeleton をつくります

mkdir serverless_blog;cd serverless_blog
npx express-generator --view=hbs
npm install ; npm i serverless-http aws-sdk
  • app.jsのコード
const serverless = require('serverless-http');
app.use('/', indexRouter);
app.use('/users',usersRouter);

//module.exports = app;
module.exports.handler = serverless(app);
  • root directory のなかにusers.jsファイルを作成
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();
var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.status(200).json({Welcome_msg:'Use create,read,update,delete routes to this Express API '});
});


router.post('/create', function(req, res, next) {
  
  const { id, firstName } = req.body;
  if (typeof id !== 'string') {
    res.status(400).json({ error: '"id" must be a string' });
  } else if (typeof firstName !== 'string') {
    res.status(400).json({ error: '"firstName" must be a string' });
  }

  const params = {
    TableName: 'Notes',
    Item: {
      "id": id,
      "firstName": firstName,
    },
    ReturnValues: 'ALL_OLD'
  };

  
  dynamoDb.put(params, (error,data) => {
    if (error) {
      console.log(error);
      res.status(400).json({ error:error });
    }else if(Object.keys(data).length === 0 && data.constructor === Object){
      res.status(200).json({Item:{id,firstName}})
    }
    else{
    res.status(400).json({Item:'Item already exist'});
    }
  });
  
  

})


router.post("/readUser", async function (req, res) {
  id=req.body.id
  res.redirect('/dev/users/readUser/' + id)
})


router.get("/read/:id", async function (req, res) {
  
  const params = {
    TableName: 'Notes',
    Key: {
      "id": req.params.id,
    }
  };

  try {
    dynamoDb.get(params, function (err, data) {
      if(err){
        res.status(400).json({ error: 'Could not read user' });
      }else if (Object.keys(data).length === 0 && data.constructor === Object) {
        
        res.status(400).json({ error: 'Item does not exist' });
      }
      else {
        res.status(200).json(data);
      }
  })
}
    
   catch (error) {
    console.log(error);
    res.status(500).json({ error: "Could not retreive user" });
  }
});

router.put('/update/:id', function(req, res, next) {
  
  const { id, firstName } = req.body;
  if (typeof id !== 'string') {
    res.status(400).json({ error: '"id" must be a string' });
  } else if (typeof firstName !== 'string') {
    res.status(400).json({ error: '"firstName" must be a string' });
  }
  
  
  const params = {
    TableName: 'Notes',
    Key: {
      "id": req.params.id
    },
    UpdateExpression: "set firstName = :y",
    ConditionExpression: "attribute_exists(id)",
    ExpressionAttributeValues:{
            ":y":firstName
    },
    ReturnValues:"UPDATED_NEW"
  };

  dynamoDb.update(params, (error,data) => {
    if (error) {
      console.log(error);
      res.status(400).json({ error:"Item does not exist for updation" });
    }
    else {
      res.status(200).json(data);
    }
    
  });
})

router.delete('/delete/:id',function(req, res, next) {
  
  const params = {
    TableName: 'Notes',
    Key: {
      "id": req.params.id,
    },
    ConditionExpression: "attribute_exists(id)",
    ReturnValues: 'ALL_OLD'
  };

  dynamoDb.delete(params, function (error,data) {
    if (error) {
      console.log(error);
      res.status(400).json({ error:"Item does not exist for deletion" });
    }
    else {
      res.status(200).json(data);
    }
  });
})

module.exports = router;

プロジェクトの root でserverless.ymlをつくります。

  • serverless.ymlのコード
service: node-serverless-api
# app and org for use with dashboard.serverless.com
app: node-serverless-api
org: jatinjmehrotra

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: "2"

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  region: ap-southeast-1
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:DescribeTable
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource: arn:aws:dynamodb:ap-southeast-1:*:*

resources:
  Resources:
    NotesTable:
      Type: "AWS::DynamoDB::Table"
      Properties:
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: "Notes"

functions:
  hello:
    handler: app.handler
    events:
      - http:
          path: /user
          method: ANY
      - http:
          path: /user/{proxy+}
          method: ANY

serverless.ymlファイルのなかに、Lambda で DynamoDB へアクセスするのための IAM Role のアクセス許可を作っています。

リソースブロックのなかには、NotesTableというなまえの DynamoDB テーブルをつくっています。

そしてイベントブロックで、Lambda をトリガーするために Path をつくっています。

NOTE: DynamoDB テーブルの Attribute Name に Reserved Name をつかわないでください。Expression Attribute Name をつかってください

Express API のデプロイとテスト

Lambda のデプロイのためにこのコマンドをじっこうしてください。

 sls deploy --aws-profile personal

NOTE: コマンドをじっこうしたあとで「profile is not configured error」というエラーメッセージがでたら、このstackoverflow の postを check をしてください

コマンドのじっこうに成功したら API の Endpoint が得られます。

API のテストのためには Curl コマンドをつかってください。もしくは、Postman をつかうこともできます。

  • POST
 curl -H "Content-Type: application/json" -X POST <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/create> -d '{"id":"1","firstName": "testuser"}'
  • GET
 curl -H "Content-Type: application/json" -X GET <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/read/1
  • PUT
 curl -H "Content-Type: application/json" -X PUT <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/update/1> -d '{"id":"1","firstName": "test123"}'
  • DELETE
curl -H "Content-Type: application/json" -X DELETE <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/delete/1> 

つくった Infrastructure は削除しましょう。

 sls remove--aws-profile personal

さいごに

Serverless Framework を使用すれば REST API をデプロイするのがとてもかんたんになります。

Serverless Framework はフレキシブルでいろいろなクラウドプロバイダーと統合できます。

Serverless Framework のほかの例はこのリンクをみてください

それでは、Happy Learning!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.